/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 *
 */

import fs from 'fs';
import path from 'path';

import { Obfuscation, ObfuscationDependencies, ObfuscationOptions } from '@ohos/hvigor-arkts-compose';

import { buildOptionManager } from '../../project/build-option/build-mode-manager.js';
import { DependencyType } from '../../project/dependency/core/dependency-interface.js';
import { ModulePkgNode } from '../../project/dependency/dependency-manager.js';
import { ModuleTargetData } from '../../tasks/data/hap-task-target-data.js';
import { TargetTaskService } from '../../tasks/service/target-task-service.js';
import { HarTargetUtil } from '../../utils/har-target-util.js';
import { HspTargetUtils } from '../../utils/hsp-target-utils.js';
import { OhosLogger } from '../../utils/log/ohos-logger.js';

const _log: OhosLogger = OhosLogger.getLogger('obfuscation');

/**
 * 获取ObfuscationOptions入口
 * 需要在hvigor-arkts-compose中补充sdkApis
 * 仅需生成obfuscation.txt时不需sdkApis
 *
 * @param service
 * @param obfuscation
 * @param cacheDir 混淆缓存目录
 */
export const getObfuscationOptionsWithCache = async (service: TargetTaskService, obfuscation: Obfuscation, cacheDir: string) => {
  const moduleModel = service.getTargetData().getModuleModel();
  if (!obfuscation) {
    return undefined;
  }

  const start = process.hrtime.bigint();
  const dependencies: ObfuscationDependencies = walkFromTree(service.getModuleService().getDependencyInfo().getModulePkgNode(), service);
  _log.debug(`Resolve obfuscation options tasks ${(process.hrtime.bigint() - start) / BigInt(1_000_000)} ms.`);

  const module = moduleModel.getModule();
  obfuscation.ruleOptions = {
    enable: obfuscation.ruleOptions?.enable,
    rules:
      obfuscation.ruleOptions?.rules ||
      transformRules(obfuscation.ruleOptions?.files, moduleModel.getProjectDir(), module.getName(), module.getBuildProfilePath()),
  };
  obfuscation.consumerRules =
    obfuscation.consumerRules || transformRules(obfuscation.consumerFiles, moduleModel.getProjectDir(), module.getName(), module.getBuildProfilePath());
  return {
    selfConfig: obfuscation,
    sdkApis: [],
    obfuscationCacheDir: cacheDir,
    exportRulePath: service.getTargetData().getPathInfo().getObfuscationExportRulePath(),
    dependencies,
  } as ObfuscationOptions;
};

const walkFromTree = (root: ModulePkgNode, service: TargetTaskService) => {
  const queue = [...root.children];
  const seen = new Set<string>();
  const dependencies = { libraries: [], hars: [], hspLibraries: [], hsps: [] } as ObfuscationDependencies;
  const depHarTargets = HarTargetUtil.calDepHarTargets(service);
  const depHspTargets = HspTargetUtils.calDepHspTargets(service);
  while (queue.length > 0) {
    const node = queue.shift()!;
    if (seen.has(node.modulePkgJsonPath)) {
      continue;
    }
    node.npm.isLocal() ? collectLocalConfigs(node, dependencies, queue, depHarTargets, depHspTargets) : collectRemoteFiles(node, dependencies);
    seen.add(node.modulePkgJsonPath);
  }
  return dependencies;
};

const collectLocalConfigs = (
  node: ModulePkgNode,
  dependencies: ObfuscationDependencies,
  queue: ModulePkgNode[],
  harTargets: Map<string, string>,
  hspTargets: Map<string, ModuleTargetData>,
) => {
  if (!node.module) {
    return;
  }
  const dependencyType = node.module.getDependencyType();
  if (dependencyType !== DependencyType.DEPENDENCY_TYPE_HAR && dependencyType !== DependencyType.DEPENDENCY_TYPE_HSP) {
    return;
  }

  const isHar = dependencyType === DependencyType.DEPENDENCY_TYPE_HAR;
  const key = isHar ? 'libraries' : 'hspLibraries';
  const target = isHar ? harTargets.get(node.moduleName) : hspTargets.get(node.moduleName)?.getTargetName();
  const buildOption = buildOptionManager.getTargetBuildOption(node.module.getModuleName(), target ?? 'default');
  const obfuscation = buildOption.arkOptions?.obfuscation;
  if (obfuscation) {
    const module = node.module.getModule();
    const realDir = node.module.getDependencyRootPath();
    const rule: Obfuscation = {
      ruleOptions: {
        enable: obfuscation.ruleOptions?.enable,
        rules: transformRules(obfuscation.ruleOptions?.files, realDir, module.getName(), module.getBuildProfilePath()),
      },
      consumerRules: transformRules(obfuscation.consumerFiles, realDir, module.getName(), module.getBuildProfilePath()),
      libDir: realDir,
    };
    dependencies[key].push(rule);
  }
  if (isHar) {
    queue.push(...node.children);
  }
};

const collectRemoteFiles = (node: ModulePkgNode, dependencies: ObfuscationDependencies) => {
  const obfuscationFile = path.resolve(node.npm.getDependencyRootPath(), 'obfuscation.txt');
  if (fs.existsSync(obfuscationFile)) {
    if (node.npm.getDependencyType() === 'har') {
      dependencies.hars.push(obfuscationFile);
    }
    if (node.npm.getDependencyType() === 'hsp') {
      dependencies.hsps.push(obfuscationFile);
    }
  }
};

/**
 * 转换混淆规则文件路径为绝对路径并检查是否存在
 *
 * @param rules 混淆规则文件
 * @param dir 模块根路径
 * @param moduleName
 * @param buildProfile
 */
const transformRules = (rules: string | string[] | undefined, dir: string, moduleName: string, buildProfile: string): string[] => {
  if (!rules) {
    return [];
  }
  const result: string[] = [];
  if (typeof rules === 'string') {
    checkObFile(rules, dir, result, moduleName, buildProfile);
    return result;
  }
  rules.forEach((rule) => checkObFile(rule, dir, result, moduleName, buildProfile));
  return result;
};

const checkObFile = (file: string, dir: string, result: string[], moduleName: string, buildProfile: string) => {
  const realFile: string = path.resolve(dir, file);
  if (!fs.existsSync(realFile)) {
    _log.printErrorExit('OBFUSCATION_FILE_LOST', [file, realFile, moduleName, buildProfile], [[file, realFile]]);
  }
  result.push(realFile);
};
